home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 090 / cmln1285.arc / DADA.PAS < prev    next >
Pascal/Delphi Source File  |  1986-02-27  |  50KB  |  1,007 lines

  1.  
  2.  
  3.     {       PROGRAM:              DADA.PAS
  4.  
  5.             AUTHOR:               Brian Hayes
  6.  
  7.             DATE BEGUN:           September 17, 1985
  8.  
  9.             FOR COMPILATION BY:   Turbo Pascal v 3.0
  10.     }
  11.     {       DESCRIPTION:
  12.             A compiler for a toy language called Dada, which adopts much
  13.             of the vocabulary of Pascal but lacks many of its features, most
  14.             notably functions, local variables, defined types and a facility
  15.             for passing parameters to procedures. The "object code" produced
  16.             by the compiler consists of Forth words. For additional detail see
  17.             the comments below and the accompanying file DADA.DOC.
  18.  
  19.             This program is intended for demonstration purposes only. It
  20.             has been compiled and casually tested, but it is certainly not
  21.             guaranteed to be error-free. Testing was done with version 3.0
  22.             of Borland International's Turbo Pascal, but I have tried to avoid
  23.             features peculiar to Borland's implementation. Adaptation to other
  24.             Pascal compilers should be easy.
  25.  
  26.             The compatibility of the object code with various Forth systems
  27.             is more difficult to assess. See the comments on the code gen-
  28.             erator and DADA.DOC Note 7.
  29.     }
  30.  
  31.     {       COMPILER DIRECTIVES  (Valid for Turbo Pascal only)   }
  32.  
  33. {$B+}   {B+ assigns StdIn/StdOut to CON, B- to TRM; default +}
  34. {$C+}   {C+ allows ^C and ^S during Read/ReadLn; default +}
  35. {$I+}   {I+ enables automatic I/O error checking; default +}
  36. {$R-}   {R+ enables run-time checking of index bounds; default -}
  37. {$V-}   {V+ requires string parameters to match declared length; default +}
  38. {$U-}   {U+ allows ^C interrupt at any time; default -}
  39. {$D+}   {D+ unbuffers I/O for devices; default +}
  40. {$F16}  {Fn sets maximum number of files open simultaneously; default 16}
  41. {$K+}   {K+ enables checking for stack-heap collision; default +}
  42.  
  43.  
  44.  
  45.  
  46. program Dada;
  47.  
  48. {*****************************************************************************}
  49. {*****************            GLOBAL CONSTANTS               *****************}
  50. {*****************************************************************************}
  51.  
  52. const
  53.  MaxIdentLen = 31;           { only the first 31 chars saved }
  54.  MaxKeyLen   =  9;           { longest keyword               }
  55.  MaxErrorLen = 35;           { longest error message         }
  56.  
  57.  
  58. {*****************************************************************************}
  59. {*****************              GLOBAL TYPES                 *****************}
  60. {*****************************************************************************}
  61.  
  62. type
  63.  IdentStr = string[MaxIdentLen];
  64.  KeyStr   = string[MaxKeyLen];
  65.  ErrorStr = string[MaxErrorLen];
  66.  ForthStr = string[64];              { for Forth output; see procedure Gen }
  67.  
  68.  
  69.     {       ErrCode identifies error messages in the array ErrorList;
  70.             see DADA.DOC Note 1.   }
  71.  
  72.  ErrCode = (Disk, QChar, XPgm, XIdent, XVar, XInt, XBool, XColon, XType, XSemi,
  73.             XBegin, XSemEnd, XThen, XDo, XAssgn, XStmt, DupDec, UnDec, Match,
  74.             XFactor, XParen, XDot, UnXEOF);
  75.  
  76.  
  77.     {       TokCode lists all symbols that can possibly be returned by the
  78.             scanner. Null is a placeholder that can appear in a few fields
  79.             of symbol-table entries.  }
  80.  
  81.  TokCode = (Null, Ident, Number, PgmSym, VarSym, ProcSym, BeginSym, EndSym,
  82.            IfSym, ThenSym, ElseSym, WhileSym, DoSym, IntSym, BoolSym, TrueSym,
  83.            FalseSym, EQ, GT, GE, NE, LE, LT, Plus, Minus, OrSym, Times, Divide,
  84.            AndSym, ModSym, NotSym, AssignOp, Colon, LeftParen, RightParen,
  85.            Semi, Dot, ReadSym, WriteSym);
  86.  
  87.  TokenRec = record        { Definition of the mailbox where the scanner }
  88.   Name  : IdentStr;       { leaves dope on the current token and where  }
  89.   Code  : TokCode;        { the parser picks it up.                     }
  90.  end;
  91.  
  92.  SymClass  = (Variable, Proc);  { Every symbol must be one or the other. }
  93.  SymPtr    = ^Symbol;           { Points to a symbol-table entry.        }
  94.  
  95.  
  96.     {    Format of a symbol-table entry. See DADA.DOC Note 2 }
  97.  
  98.  Symbol = record
  99.   Name    : IdentStr;      { UpCase string of name as read }
  100.   Class   : SymClass;      { either Variable or Proc       }
  101.   VarType : TokCode;       { either IntSym or BoolSym      }
  102.   Scope   : integer;       { zero for global, then 1,2,3...}
  103.   Next    : SymPtr;        { pointer to next table entry   }
  104.  end;
  105.  
  106.  
  107.     {    The output buffer represents a Forth "screen" of 16 lines
  108.          by 64 characters.    }
  109.  
  110.  OutBufLines = 1..16;
  111.  OutBufChars = 1..64;
  112.  OutBufArray = array[OutBufLines] of array[OutBufChars] of char;
  113.  
  114.  
  115. {*****************************************************************************}
  116. {*****************            GLOBAL VARIABLES               *****************}
  117. {*****************************************************************************}
  118.  
  119. var
  120.  OutLine  : OutBufLines;        { Declared global becaused called by }
  121.  OutPoint : OutBufChars;        { both InitOutBuf and Gen.           }
  122.  OutBuf   : OutBufArray;
  123.  
  124.  InFile  : Text;                 { source code }
  125.  OutFile : file of OutBufArray;  { object code }
  126.  
  127.  TK : TokenRec;         { where dope on the current token is stashed }
  128.  CH : char;             { current scanner input }
  129.  LineCount : integer;   { number of lines in source text }
  130.  
  131.  TypeSet   : set of TokCode;        { sets defined for convenience }
  132.  TFset     : set of TokCode;        { in the parsing logic         }
  133.  RelOpSet  : set of TokCode;
  134.  AddOpSet  : set of TokCode;
  135.  MultOpSet : set of TokCode;
  136.  
  137.  FirstSym : SymPtr;        { link to the start of the symbol-table chain }
  138.  
  139.  CurrentScope : integer;   { nesting depth of procedures }
  140.  
  141.  Keywords  : array[TokCode] of KeyStr;
  142.  ErrorList : array[ErrCode] of ErrorStr;
  143.  
  144.  
  145. {*****************************************************************************}
  146. {*****************************************************************************}
  147. {*****************                                           *****************}
  148. {*****************             UTILITY ROUTINES              *****************}
  149. {*****************                                           *****************}
  150. {*****************************************************************************}
  151. {*****************************************************************************}
  152.  
  153.     {    The Keywords and ErrorList arrays must be initialized when the
  154.          program is started. So must the five small sets used to form
  155.          symbols into groups.    }
  156.  
  157. procedure InitKeywords;
  158.  begin
  159.   Keywords[PgmSym]   := 'PROGRAM';
  160.   Keywords[VarSym]   := 'VAR';
  161.   Keywords[IntSym]   := 'INTEGER';
  162.   Keywords[BoolSym]  := 'BOOLEAN';
  163.   Keywords[BeginSym] := 'BEGIN';
  164.   Keywords[EndSym]   := 'END';
  165.   Keywords[IfSym]    := 'IF';
  166.   Keywords[ThenSym]  := 'THEN';
  167.   Keywords[ElseSym]  := 'ELSE';
  168.   Keywords[WhileSym] := 'WHILE';
  169.   Keywords[DoSym]    := 'DO';
  170.   Keywords[NotSym]   := 'NOT';
  171.   Keywords[OrSym]    := 'OR';
  172.   Keywords[AndSym]   := 'AND';
  173.   Keywords[ModSym]   := 'MOD';
  174.   Keywords[ProcSym]  := 'PROCEDURE';
  175.   Keywords[TrueSym]  := 'TRUE';
  176.   Keywords[FalseSym] := 'FALSE';
  177.   Keywords[ReadSym]  := 'READLN';
  178.   Keywords[WriteSym] := 'WRITELN';
  179.  end;
  180.  
  181. procedure InitErrorList;
  182.  begin
  183.   ErrorList[Disk   ] := 'Trouble with file or disk.';
  184.   ErrorList[QChar  ] := 'Unrecognized character in input.';
  185.   ErrorList[Xpgm   ] := 'No program header.';
  186.   ErrorList[XIdent ] := 'Identifier expected.';
  187.   ErrorList[XVar   ] := 'Variable expected.';
  188.   ErrorList[XInt   ] := 'Integer value expected.';
  189.   ErrorList[XBool  ] := 'Boolean value expected.';
  190.   ErrorList[XColon ] := 'Colon expected.';
  191.   ErrorList[XType  ] := 'Type designator expected.';
  192.   ErrorList[XSemi  ] := 'Semicolon expected.';
  193.   ErrorList[XBegin ] := '"Begin" expected.';
  194.   ErrorList[XSemEnd] := 'Semicolon or "end" expected.';
  195.   ErrorList[XThen  ] := '"Then" expected.';
  196.   ErrorList[XDo    ] := '"Do" expected.';
  197.   ErrorList[XAssgn ] := 'Assignment statement expected.';
  198.   ErrorList[XStmt  ] := 'Statement expected.';
  199.   ErrorList[DupDec ] := 'Duplicate declaration.';
  200.   ErrorList[UnDec  ] := 'Undeclared variable or procedure.';
  201.   ErrorList[Match  ] := 'Type mismatch.';
  202.   ErrorList[XFactor] := 'Factor expected.';
  203.   ErrorList[XParen ] := 'Closing parenthesis expected.';
  204.   ErrorList[XDot   ] := 'Period expected.';
  205.   ErrorList[UnXEOF ] := 'Unexpected end of file.';
  206.  end;
  207.  
  208. procedure InitSets;
  209.  begin
  210.   TypeSet   := [IntSym, BoolSym];
  211.   TFset     := [TrueSym, FalseSym];
  212.   RelOpSet  := [EQ..LT];
  213.   AddOpSet  := [Plus..OrSym];
  214.   MultOpSet := [Times..ModSym];
  215.  end;
  216.  
  217.  
  218.     {    The error-handling given here is minimal. Procedure Error is
  219.          handed a code and prints the corresponding string. The only
  220.          information supplied on what might have caused the error is
  221.          a line number. The program then halts. See DADA.DOC Note 3.   }
  222.  
  223. procedure Error(Problem : ErrCode);
  224.  begin
  225.   WriteLn('ERROR IN LINE ',LineCount,':  ',ErrorList[Problem]);
  226.   WriteLn; WriteLn('Compilation aborted.');
  227.   Halt;
  228.  end;
  229.  
  230. procedure SayHello;
  231.  begin
  232.   ClrScr;
  233.   WriteLn;
  234.   WriteLn;
  235.   WriteLn('DADA: A demonstration compiler');
  236.   WriteLn;
  237.   WriteLn('This program is described in Computer Language, December, 1985');
  238.   WriteLn;
  239.   WriteLn;
  240.   WriteLn;
  241.  end;
  242.  
  243.  
  244.     {    The file handling is as rudimentary as the error routine. Further-
  245.          more, the version given here depends on features peculiar to Turbo
  246.          Pascal. See DADA.DOC Note 4.      }
  247.  
  248. procedure OpenFiles;
  249.  var
  250.   FileOK : boolean;
  251.   InFileName  : string[14];
  252.   OutFileName : string[14];
  253.  begin
  254.   Write('Enter the name of the file to be compiled: ');
  255.   ReadLn(InFileName);
  256.   Assign(InFile, InFileName);
  257.   {$I-} Reset(InFile); {$I+}
  258.   FileOK := (IoResult = 0); if not FileOK then Error(Disk);
  259.   WriteLn;
  260.   Write('Enter the name of the output file:         ');
  261.   ReadLn(OutFileName);
  262.   Assign(OutFile, OutFileName);
  263.   {$I-} ReWrite(OutFile); {$I+};
  264.   FileOK := (IoResult = 0); if not FileOK then Error(Disk);
  265.   WriteLn; WriteLn;
  266.  end;
  267.  
  268. procedure CloseFiles;
  269.  begin
  270.   Close(InFile);
  271.   Close(OutFile);
  272.  end;
  273.  
  274.  
  275. {*****************************************************************************}
  276. {*****************************************************************************}
  277. {*****************                                           *****************}
  278. {*****************             LEXICAL ANALYZER              *****************}
  279. {*****************                                           *****************}
  280. {*****************************************************************************}
  281. {*****************************************************************************}
  282.  
  283.     {     Procedure GetTK constitutes the scanner, or lexical analyzer. It
  284.           calls on GetCH to read the next character from the input stream
  285.           and uses Recognize to set up the two global-variable fields
  286.           TK.Code and TK.Name. The main routine first strips out all com-
  287.           ments and whitespace characters and then enters a state deter-
  288.           mined by the first character of the remaining input. Each state
  289.           corresponds to one clause of the case statement. Note that GetCH
  290.           converts all alphabetic characters to upper case, so that the
  291.           compiler in insensitive to case. Two errors can be issued by the
  292.           scanner. Error(QChar) is called if a character outside the recog
  293.           nized set appears in the input (except in comments). Error(UnXEOF)
  294.           is reported if the scanner encounters end-of-file. Since GetTK
  295.           is not called after the final period marking the end of a pro-
  296.           gram, the scanner should never read the end of the file.
  297.  
  298.           NOTE: Each time the scanner is called, it goes to work on the
  299.           character already in variable CH, not on the next character
  300.           from the stream. When GetTK exits, CH holds the first char-
  301.           acter beyond the token returned. In other words, the file
  302.           pointer is pre-incremented.
  303.  
  304.           See also DADA.DOC Note 5.      }
  305.  
  306. procedure GetTK;
  307.  var
  308.   I : TokCode;                     { used in a FOR loop to check for keywords }
  309.  
  310.  procedure GetCH;
  311.   begin
  312.    if Eof(InFile) then CH := #0 else Read(InFile, CH);  { get next if possible}
  313.    CH := Upcase(CH);                                    { make case immaterial}
  314.    if CH = #13 then LineCount := LineCount + 1;         { count for Error     }
  315.   end;
  316.  
  317.  procedure Recognize(Tok: TokCode);        { Called once for each character   }
  318.   begin                                    { scanned, adding it to the string }
  319.    TK.Code := Tok;                         { in TK.Name and recording the     }
  320.    TK.Name := Concat(TK.Name,CH);          { current analysis in TK.Code. Note}
  321.    GetCH;                                  { that TK.Code is not actually     }
  322.   end;                                     { valid until GetTK returns.       }
  323.  
  324.  
  325.     {    The first section of GetTK strips out comments and the whitespace
  326.          characters #9 (tab), #10 (line feed), #12 (form feed), #13 (carriage
  327.          return) and $32 (space). For comments any characters following a
  328.          left brace are ignored up to the first right brace. Note that this
  329.          means comments cannot be nested: Any number of opening braces will
  330.          be canceled by the first closing brace. The nested while loops are
  331.          needed because comments and whitespace can be interspersed in any
  332.          sequence.      }
  333.  
  334.  begin   { GetTK }
  335.   while (CH in ['{',#9,#10,#12,#13,#32]) do       { loop while comment, space }
  336.    begin
  337.     if CH = '{' then repeat GetCH until CH = '}'; { eat up the comment        }
  338.     GetCH;                                        { toss out the right brace  }
  339.     while (CH in [#9,#10,#12,#13,#32]) do GetCH;  { eat the whitespace        }
  340.    end;
  341.   TK.Name := '';                        { reset the identifier string to null }
  342.   case CH of                            { look at the current char from stream}
  343.    'A'..'Z' : begin                                         { Ident or keyword}
  344.                while (CH in ['A'..'Z','0'..'9']) do         { add chars to the}
  345.                 Recognize(Ident);                           { TK.Name string  }
  346.                for I := PgmSym to WriteSym do               { An Ident unless }
  347.                 if Keywords[I] = TK.Name then TK.Code := I; { listed here     }
  348.               end;
  349.    '0'..'9' : while (CH in ['0'..'9']) do Recognize(Number); { numeric literal}
  350.    '>' : begin
  351.           Recognize(GT);                              { With two-symbol oper- }
  352.           if CH = '=' then Recognize(GE);             { ators, start by assum-}
  353.          end;                                         { ing the one-symbol    }
  354.    '<' : begin                                        { form and then revise  }
  355.           Recognize(LT);                              { the verdict if the    }
  356.           if CH = '>' then Recognize(NE)              { second character is   }
  357.           else if CH = '=' then Recognize(LE)         { found.                }
  358.          end;
  359.    ':' : begin
  360.           Recognize(Colon);
  361.           if CH = '=' then Recognize(AssignOp);
  362.          end;
  363.    '=' : Recognize(EQ);
  364.    '+' : Recognize(Plus);
  365.    '-' : Recognize(Minus);
  366.    '*' : Recognize(Times);
  367.    '/' : Recognize(Divide);
  368.    '(' : Recognize(LeftParen);
  369.    ')' : Recognize(RightParen);
  370.    ';' : Recognize(Semi);
  371.    '.' : Recognize(Dot);
  372.    #0  : Error(UnXEOF);      { Program has ended without a period }
  373.    else  Error(QChar);       { Queer character; can't digest it }
  374.   end;
  375.  end;
  376.  
  377.  
  378. {*****************************************************************************}
  379. {*****************************************************************************}
  380. {*****************                                           *****************}
  381. {*****************               SYMBOL TABLE                *****************}
  382. {*****************                                           *****************}
  383. {*****************************************************************************}
  384. {*****************************************************************************}
  385.  
  386.     {    The three routines Find, Declare and Blot manage the symbol
  387.          table. The table is organized as a linked list in which
  388.          FirstSym always points to the most recently added entry. The
  389.          Next field points to the next-youngest entry, so that
  390.          following the chain of Nexts ultimately leads to the first
  391.          entry, which is always the declaration of the program
  392.          header. Because all variables in Dada are global, the
  393.          symbol table has a fixed, predictable structure: the program
  394.          declaration is followed by variable declarations and then by
  395.          procedure declarations. See DADA.DOC Note 6.   }
  396.  
  397.     {    Find is passed an identifier string and returns either a
  398.          pointer to the corresponding symbol-table entry or nil if
  399.          the identifier does not exist. It traverses the chain of
  400.          entries beginning with FirstSym, and so the first matching
  401.          entry will be found.   }
  402.  
  403. function Find(ID: IdentStr): SymPtr;
  404.  var
  405.   ThisSym : SymPtr;
  406.  begin
  407.   ThisSym := FirstSym;                    { start with the latest entry     }
  408.   while ((ID<>ThisSym^.Name) and          { loop if no match and...         }
  409.         (ThisSym<>nil)) do                { we're not at the end of list    }
  410.           ThisSym := ThisSym^.Next;       { get next record                 }
  411.   Find := ThisSym;                        { a match if there is one, or nil }
  412.  end;
  413.  
  414.  
  415.     {    Declare installs both variable names and procedure names in
  416.          the symbol table. ID is the name of the Identifier, as given
  417.          in TK.Name; CL is either "Proc" or "Variable"; Kind is "IntSym"
  418.          or "BoolSym" for variables, "Null" for procedures.  }
  419.  
  420. procedure Declare(ID: IdentStr; CL: SymClass; Kind: TokCode);
  421.  var
  422.   ThisSym : SymPtr;
  423.  begin
  424.   ThisSym := Find(ID);                        { See if it already exists     }
  425.   if ThisSym <> nil then Error(DupDec);       { Call error & halt if it does }
  426.   New(ThisSym);                               { Create a new record          }
  427.   ThisSym^.Next := FirstSym;                  { Swap pointers to put the...  }
  428.   FirstSym := ThisSym;                        { ...new record first in list  }
  429.   with FirstSym^ do
  430.    begin
  431.     Name    := ID;                            { Plug in the values passed... }
  432.     Class   := CL;                            { ...as arguments...           }
  433.     VarType := Kind;
  434.     Scope   := CurrentScope;                  { ...and a value from a global }
  435.    end;
  436.  end;
  437.  
  438.  
  439.     {    Blot is called when the "end" of a block is reached and removes
  440.          from the symbol table all names whose scope is confined to that
  441.          block. The global variable CurrentScope is initialized to zero
  442.          and incremented each time ParseBlock is called. Blot decrements
  443.          CurrentScope and unlinks from the symbol table any entry whose
  444.          Scope field is numerically greater than CurrentScope.   }
  445.  
  446. procedure Blot;
  447.  var
  448.   TrashSym : SymPtr;
  449.  begin
  450.   CurrentScope := CurrentScope - 1;        { back to scope of next outer block}
  451.   while FirstSym^.Scope > CurrentScope do  { erase entries for closed block   }
  452.    begin
  453.     TrashSym := FirstSym;                         { Give the pointer an alias }
  454.     FirstSym := FirstSym^.Next;                   { Unlink the record         }
  455.     Dispose(TrashSym);                            { Free the allocated memory }
  456.    end;
  457.  end;
  458.  
  459.  
  460. {*****************************************************************************}
  461. {*****************************************************************************}
  462. {*****************                                           *****************}
  463. {*****************              CODE GENERATOR               *****************}
  464. {*****************                                           *****************}
  465. {*****************************************************************************}
  466. {*****************************************************************************}
  467.  
  468.     {    The code generator is simple to the point of triviality, largely
  469.          because the Forth virtual machine offers a very powerful assembly
  470.          language. All address calculations, for instance, are done by the
  471.          Forth interpreter. With a Forth system that accepts input as a
  472.          sequence of CR/LF-delimited lines, the code generator could be
  473.          reduced to a one-line procedure: WriteLn(OutFile,Forth). The
  474.          routines given here produce Forth "screens," or blocks of 1,024
  475.          bytes filled out with blanks (ASCII #32). For more on this for-
  476.          matting see DADA.DOC Note 7.
  477.  
  478.          The main procedure of the code generator is Gen, which is called
  479.          by the various parsing routines; the argument is a string to be
  480.          written to the output file. The string is actually appended to
  481.          a buffer that holds 16 lines of 64 characters (the standard .SCR
  482.          format). When a line exceeds 62 characters, a new line is started;
  483.          when line 16 is reached, the continuation symbol "-->" is written
  484.          and the buffer is flushed to the disk and then reset to all blanks.
  485.          To make the generated code more readable, the symbol "|" is defined
  486.          as a control character that forces Gen to start a new line. The
  487.          parsing routines issue Gen('|') after each colon definition.
  488.  
  489.          InitOutBuf sets the 1,024 bytes of the output buffer to the
  490.          ASCII blank character (#32) and resets the two array indices
  491.          OutLine and OutPoint to 1, which corresponds to the upper
  492.          left-hand corner of a Forth screen. The procedure is not made
  493.          local to Gen because it is called from the main initializing
  494.          routine at program startup.    }
  495.  
  496. procedure InitOutBuf;
  497.  begin
  498.   for OutLine := 1 to 16 do
  499.    for OutPoint := 1 to 64 do OutBuf[Outline,OutPoint] := #32;
  500.   OutLine := 1; OutPoint := 1;
  501.  end;
  502.  
  503. procedure Gen(Forth : ForthStr);
  504.  var
  505.   FileOK : boolean;
  506.   I, TempPoint, TempLine : integer;   { two temps for testing length }
  507.  
  508.  
  509.     {    WriteBuf, like OpenFiles, is written with a Turbo-specific
  510.          error-checking method. It simply writes the accumulated
  511.          buffer to the output file and, if there is no disk error,
  512.          calls InitOutBuf to reinitialize the array.    }
  513.  
  514.  procedure WriteBuf;
  515.   begin
  516.    {$I-} Write(OutFile,OutBuf); {$I+}
  517.    FileOK := (IoResult = 0); if not FileOK then Error(Disk);
  518.    InitOutBuf;
  519.   end;
  520.  
  521.  
  522.     {    NewLine resets the character counter and tests the line count;
  523.          if we are on line 15, the recursive call Gen('-->') flushes
  524.          the buffer and starts a new screen.   }
  525.  
  526.  procedure NewLine;
  527.   begin
  528.    OutPoint := 1; TempLine := OutLine + 1;
  529.    if TempLine >= 15 then Gen('-->') else OutLine := TempLine;
  530.   end;
  531.  
  532.  begin   { Gen }
  533.   if Forth = '|' then begin NewLine; exit; end;  { force new line & leave     }
  534.   TempPoint := OutPoint + Length(Forth);         { Temp avoids out-of-range   }
  535.   if TempPoint > 62 then NewLine;                { 62 (not 64) to allow blanks}
  536.   for I := 1 to Length(Forth) do
  537.    begin
  538.     OutBuf[OutLine,OutPoint] := Forth[I];        { copy the string into buffer}
  539.     OutPoint := OutPoint + 1;
  540.    end;
  541.   OutPoint := OutPoint + 1;                      { allow one blank after code }
  542.   if ((Forth = '-->') or (Forth = ';S')) then WriteBuf;
  543.  end;
  544.  
  545.  
  546.     {    GenHeader creates a "run-time library" that precedes the object
  547.          code for all Dada programs. Some Forth systems may need additional
  548.          or different definitions here. The READ routine provides keyboard
  549.          input of signed integers. It could readily be improved.   }
  550.  
  551. procedure GenHeader(PgmName : IdentStr);
  552.  begin
  553.   Gen('( Output of Dada compiler )'); Gen('|');    {  Screen 0 comments     }
  554.   Gen(Concat('( To execute type: 1 LOAD ',PgmName,' )')); Gen(';S');
  555.   Gen('FORTH DEFINITIONS DECIMAL'); Gen('|');
  556.   Gen('1 CONSTANT TRUE  ');                        {                        }
  557.   Gen('0 CONSTANT FALSE');                         {  These synonyms will   }
  558.   Gen(': NEGATE MINUS ;'); Gen('|');               {  not be needed by all  }
  559.   Gen(': NOT 0= ;');                               {  Forth systems; others }
  560.   Gen(': <> = NOT ;');                             {  may be required.      }
  561.   Gen(': >= < NOT ;');                             {                        }
  562.   Gen(': <= > NOT ;'); Gen('|');                   {                        }
  563.   Gen(': READ KEY DUP 45 = IF TRUE SWAP EMIT KEY ELSE FALSE SWAP'); Gen('|');
  564.   Gen('  THEN 0 SWAP BEGIN DUP 13 = NOT WHILE DUP 48 < OVER'); Gen('|');
  565.   Gen('  57 > OR IF DROP 7 EMIT ELSE DUP EMIT 48 - SWAP 10 * +'); Gen('|');
  566.   Gen('  THEN KEY REPEAT DROP SWAP IF NEGATE THEN SWAP ! ;'); Gen('|');
  567.   Gen(': WRITE @ . CR ;');
  568.   Gen('-->');
  569.  end;
  570.  
  571.  
  572. {*****************************************************************************}
  573. {*****************************************************************************}
  574. {*****************                                           *****************}
  575. {*****************                  PARSER                   *****************}
  576. {*****************                                           *****************}
  577. {*****************************************************************************}
  578. {*****************************************************************************}
  579.  
  580.     {    ParseProgram and the routines nested under it constitute the main
  581.          driver of DADA.PAS. The organization is outlined in DADA.DOC Note 8.
  582.          Each routine calls on GetTK (the scanner). Statements haveing to do
  583.          with parsing proper are interleaved with those for type checking
  584.          and code generation.    }
  585.  
  586. procedure ParseProgram;
  587.  var
  588.   HoldID : IdentStr;                { hangs onto the program name }
  589.  
  590.  
  591.     {    ParseVariables is called once by ParseProgram. If the current
  592.          token is not "var," there are no variables in the program and
  593.          the routine exits. Otherwise each declaration is checked for
  594.          proper form and a statement "0 VARIABLE IDENT" is generated to
  595.          allocate 16 bits of storage and record its address under the
  596.          name IDENT in the Forth dictionary.   }
  597.  
  598.  procedure ParseVariables;
  599.   var
  600.    HoldVar : IdentStr;
  601.   begin
  602.    if TK.Code = VarSym then             { else no variables in entire program }
  603.     begin
  604.      GetTK;                                             { eat the "var" token }
  605.      repeat                          { loop for arbitrary number of variables }
  606.       if TK.Code <> Ident then Error(XIdent);      { format is "Ident: Type;" }
  607.       HoldVar := TK.Name; GetTK;                   { hang onto identifier     }
  608.       if TK.Code <> Colon then Error(XColon); GetTK;
  609.       if not (TK.Code in TypeSet) then Error(XType); { TypeSet=IntSym,BoolSym }
  610.       Declare(HoldVar,Variable,TK.Code); GetTK;      { install in symbol table}
  611.       Gen(Concat('0 VARIABLE ',HoldVar)); Gen('|');  { gen code & new line    }
  612.       if TK.Code <> Semi then Error(XSemi); GetTK;   { every decl. must have  }
  613.      until (TK.Code in [ProcSym,BeginSym]);          { no more variables      }
  614.     end;
  615.   end;
  616.  
  617.  procedure ParseBlock(Caller: IdentStr);    { "Caller" will be the Ident gen-}
  618.   var                                       { erated when "begin" is reached.}
  619.    HoldID : IdentStr;                       { HoldID passed as Caller to     }
  620.                                             { next nested block.             }
  621.  
  622.   procedure ParseStatement;
  623.    var
  624.     IdentPtr : SymPtr;                      { used to check symbol table      }
  625.     HoldID   : IdentStr;                    { hold while class & type checked }
  626.     HoldType : TokCode;                     { hold while exp. type is checked }
  627.  
  628.  
  629.     {    All the routines from ParseExpression on down are defined as
  630.          functions rather than procedures. They return the type (integer
  631.          or boolean) deduced from the operations specified. The "HoldOp"
  632.          variables are needed to delay code generation for postfix notation.
  633.          The "HoldType" variables record the type of the first operand so
  634.          that it can be compared with the type of the second operand.   }
  635.  
  636.    function ParseExpression: TokCode;
  637.     var
  638.      HoldRelOp : IdentStr;
  639.      HoldType  : TokCode;
  640.  
  641.     function ParseSimpleExpr: TokCode;
  642.      var
  643.       HoldAddOp : IdentStr;
  644.       HoldType  : TokCode;
  645.  
  646.      function ParseTerm: TokCode;
  647.       var
  648.        HoldMultOp : IdentStr;
  649.        HoldType   : TokCode;
  650.  
  651.       function ParseSignedFactor: TokCode;
  652.        var
  653.         IdentPtr : SymPtr;
  654.         HoldType : TokCode;
  655.  
  656.  
  657.     {    ParseFactor is the lowest-level routine in the parser. For a factor
  658.          to be recognized as valid it must be either a boolean literal (TRUE
  659.          of FALSE), a numeric literal, an identifier that designates a var-
  660.          iable or a parenthesized expression. The case statement considers
  661.          each of these possibilities in turn.   }
  662.  
  663.        function ParseFactor: TokCode;
  664.         var
  665.          IdentPtr: SymPtr;               { needed to consult the symbol table }
  666.         begin
  667.          case TK.Code of
  668.           TrueSym,
  669.           FalseSym  : begin
  670.                        ParseFactor := BoolSym;          { return type boolean }
  671.                        Gen(TK.Name); GetTK;             { Gen TRUE or FLASE   }
  672.                       end;
  673.           Number    : begin
  674.                        ParseFactor := IntSym;           { return type integer }
  675.                        Gen(TK.Name); GetTK;             { Gen numeric literal }
  676.                       end;
  677.           Ident     : begin
  678.                        IdentPtr := Find(TK.Name);          { look up the name }
  679.                        if IdentPtr = nil then Error(UnDec)       { not found? }
  680.                        else begin
  681.                         if IdentPtr^.Class <> Variable        { can't be proc }
  682.                          then Error(XVar)
  683.                          else begin
  684.                           ParseFactor := IdentPtr^.VarType; { rtn Int or Bool }
  685.                           Gen(ConCat(TK.Name,' @')); GetTK; { code to fetch   }
  686.                         end;
  687.                        end;
  688.                       end;
  689.           LeftParen : begin                { call ParseExpression recursively }
  690.                        GetTK;                           { and return the type }
  691.                        ParseFactor := ParseExpression;      { that it returns }
  692.                        if TK.Code <> RightParen then Error(XParen);
  693.                        GetTK;                                   { eat the ")" }
  694.                       end;
  695.           else        Error(XFactor);  { if none of above, not a valid factor }
  696.          end;
  697.         end;
  698.  
  699.  
  700.     {    ParseSignedFactor is introduced into the chain of expression-
  701.          parsing functions merely to handle a unary plus, minus or logical
  702.          NOT preceding a factor. If none of these is found, the code drops
  703.          through directly to ParseFactor. If one of them is found, the
  704.          appropriate code is generated after ParseFactor returns, thereby
  705.          converting the notation to postfix form.    }
  706.  
  707.        begin   {ParseSignedFactor}
  708.         case TK.Code of
  709.          Plus      : begin
  710.                       GetTK;                               { eat the + sign   }
  711.                       HoldType := ParseFactor;             { parse & get type }
  712.                       if HoldType <> IntSym
  713.                        then Error(XInt)                    { +boolean illegal }
  714.                        else ParseSignedFactor := IntSym;   { HoldType=Int     }
  715.                      end;
  716.          Minus     : begin
  717.                       GetTK;                               { eat the - sign   }
  718.                       HoldType := ParseFactor;             { parse & get type }
  719.                       if HoldType <> IntSym
  720.                        then Error(XInt)                    { -boolean illegal }
  721.                        else begin
  722.                         ParseSignedFactor := IntSym;       { HoldType = Int   }
  723.                         Gen('NEGATE');                     { code toggles sign}
  724.                        end;
  725.                      end;
  726.          NotSym    : begin
  727.                       GetTK;                              { eat NOT symbol    }
  728.                       HoldType := ParseFactor;            { parse & get type  }
  729.                       if HoldType <> BoolSym
  730.                        then Error(XBool)                  { NOT number illegal}
  731.                        else begin
  732.                         ParseSignedFactor := BoolSym;     { HoldType = boolean}
  733.                         Gen('NOT');                       { code to invert    }
  734.                        end;
  735.                      end;
  736.          else        ParseSignedFactor := ParseFactor;    { no +, -, NOT found}
  737.         end;
  738.        end;
  739.  
  740.  
  741.     {    ParseTerm recognizes either "SignedFactor" or a subexpression of
  742.          the form "SignedFactor MultOp Term". Thus it will always call
  743.          ParseSignedFactor, and if the next token is a MultOp, it will
  744.          also call itself recursively.    }
  745.  
  746.     {    For a lacuna in type-checking, see DADA.DOC Note 9.    }
  747.  
  748.       begin   {ParseTerm}
  749.        HoldType := ParseSignedFactor;     { parse & get type first operand }
  750.        if (TK.Code in MultOpSet) then     { TK = *, /, OR?                 }
  751.         begin
  752.          HoldMultOp := TK.Name;           { save the Op for postfix        }
  753.          GetTK;                           { and eat it                     }
  754.          if not (HoldType = ParseTerm)    { parse & get type 2d operand    }
  755.           then Error(Match);              { 1st & 2d operands same type?   }
  756.          Gen(HoldMultOp);                 { issue the saved operator       }
  757.         end;
  758.        ParseTerm := HoldType;             { return the operand type        }
  759.       end;
  760.  
  761.  
  762.     {    ParseSimpleExpr recognizes either "Term" or a subexpression of
  763.          the form "Term AddOp SimpleExpr". It always calls ParseTerm
  764.          and if the next token is an AddOp, it also calls itself.    }
  765.  
  766.      begin   {ParseSimpleExpr}
  767.       HoldType := ParseTerm;                   { parse & get type 1st operand }
  768.       if (TK.Code in AddOpSet) then            { TK = +, -, AND?              }
  769.        begin
  770.         HoldAddOp := TK.Name;                  { save the Op for postfix      }
  771.         GetTK;                                 { and eat it                   }
  772.         if not (HoldType = ParseSimpleExpr)    { parse & get type 2d operand  }
  773.          then Error(Match);                    { 1st & 2d operands same type? }
  774.         Gen(HoldAddOp);                        { issue the save operator      }
  775.        end;
  776.       ParseSimpleExpr := HoldType;             { return the operand type      }
  777.      end;
  778.  
  779.  
  780.     {    ParseExpression recognizes either "SimpleExpr" or a sub-
  781.          expression of the form "SimpleExpr RelOp SimpleExpr." It always
  782.          calls ParseSimpleExpr once, and if the next token is a RelOp, it
  783.          also makes a second call to ParseSimpleExpr. Note that this scheme
  784.          is slightly different from the recursive pattern in the lower-
  785.          level functions. On that model one would expect "SimpleExpr RelOp
  786.          Expression," so that to parse the second operand the function would
  787.          call itself. Such a construction, however, would allow expressions
  788.          of the form A > B < C = D, and so on. It would be easy enough to
  789.          assign a meaning to these expressions, but the language definition
  790.          does not supply one.    }
  791.  
  792.     begin                           {ParseExpression}
  793.      HoldType := ParseSimpleExpr;        { parse & get type 1st operand    }
  794.      ParseExpression := HoldType;        { type to be returned if no RelOp }
  795.      if (TK.Code in RelOpSet) then       { TK is >, <, =, etc. ?           }
  796.       begin
  797.        HoldRelOp := TK.Name;             { save operator for postfix       }
  798.        GetTK;                            { and eat it                      }
  799.        if not (HoldType = ParseSimpleExpr) { parse & get type 2d operand   }
  800.         then Error(Match);               { 1st & 2d operands same type ?   }
  801.        ParseExpression := BoolSym;       { if Expr has Relop, type = bool  }
  802.        Gen(HoldRelOp);                   { issue the saved operator        }
  803.       end;
  804.     end;
  805.  
  806.  
  807.     {    ParseStatement is the most elaborate routine in the parser. The
  808.          grammar for Dada specifies five constructs to be recognized as
  809.          valid statements: a compound statement delimited by "begin" and
  810.          "end," an assignment statement, a procedure call, an "if" state-
  811.          ment and a "while" statement. The parser actually includes two
  812.          more possibilities: "Read" and "Write" statements, which can be
  813.          viewed as predefined procedures. With one exception the grammar
  814.          allows these possibilities to be distinguished on the basis of
  815.          the first token presented to ParseStatement. The exception is
  816.          the discrimination between assignment statements and procedure
  817.          calls, which both begin with an identifier. The parser chooses
  818.          its path by checking the identifier's class in the symbol table:
  819.          a value can be assigned only to a variable, and only a procedure
  820.          can be called.     }
  821.  
  822.     {    See also DADA.DOC Note 10    }
  823.  
  824.    begin   {ParseStatement}
  825.     case TK.Code of
  826.      BeginSym : begin                                 { must be compound   }
  827.                  GetTK;                               { eat the "BEGIN"    }
  828.                  while TK.Code <> EndSym do           { loop while stmts   }
  829.                   begin
  830.                    ParseStatement;                    { calls itself       }
  831.                    if not (TK.Code in [Semi,EndSym])  { delimiter expected }
  832.                     then Error(XSemEnd);
  833.                    if TK.Code = Semi then GetTK;      { go back for another}
  834.                   end;
  835.                  GetTK;                         { TK must be "END"; eat it }
  836.                 end;
  837.      IfSym    : begin                                { must be If statement}
  838.                  GetTK;                              { eat the "IF"        }
  839.                  if not (BoolSym = ParseExpression)  { parse expr & ck type}
  840.                   then Error(XBool);                 { only boolean allowed}
  841.                  Gen('IF');                          { Forth IF after expr }
  842.                  if TK.Code <> ThenSym               { must have then part }
  843.                   then Error(XThen); GetTK;          { if present, eat it  }
  844.                  ParseStatement;                     { calls itself        }
  845.                  if TK.Code = ElseSym then           { else is optional    }
  846.                   begin                              { if present, Gen code}
  847.                    Gen('ELSE'); GetTK;               { and eat the token   }
  848.                    ParseStatement;                   { calls itself again  }
  849.                   end;
  850.                  Gen('THEN');                        { end of Forth cond.  }
  851.                 end;
  852.      WhileSym : begin                                  { this is a while loop}
  853.                  Gen('BEGIN'); GetTK;                  { Gen marker; eat tok }
  854.                  if not (BoolSym = ParseExpression)    { parse and check type}
  855.                   then Error(XBool);                   { must be boolean     }
  856.                  if TK.Code <> DoSym then Error(XDo);  { must have Do part   }
  857.                  Gen('WHILE'); GetTK;                  { eat; Gen Forth test }
  858.                  ParseStatement;                       { recursive call      }
  859.                  Gen('REPEAT');                        { end of Forth block  }
  860.                 end;
  861.      Ident    : begin                                  { assignment or call  }
  862.                  IdentPtr := Find(TK.Name);            { look up in table    }
  863.                  if IdentPtr = nil then Error(UnDec);  { can't find it       }
  864.                  if IdentPtr^.Class = Variable then    { must be assignment  }
  865.                   begin
  866.                    HoldType := IdentPtr^.VarType;      { save Ident type...  }
  867.                    HoldID := TK.Name; GetTK;           { and name for postfix}
  868.                    if TK.Code <> AssignOp              { must have := sign   }
  869.                     then Error(XAssgn); GetTK;         { if so, eat it       }
  870.                    if not (HoldType = ParseExpression) { parse expr & ck type}
  871.                     then Error(Match);                 { report mismatch     }
  872.                    Gen(Concat(HoldID,' !'));           { code to store value }
  873.                   end
  874.                  else                                  { must be proc call   }
  875.                   begin                                { invoke the Forth    }
  876.                    Gen(TK.Name); GetTK;                { word and consume    }
  877.                   end;                                 { the token           }
  878.                 end;
  879.      ReadSym  : begin                               { predefined READ proc   }
  880.                  GetTK;                             { eat token              }
  881.                  if TK.Code <> Ident                { must name variable...  }
  882.                   then Error(XIdent);               { to hold the value read }
  883.                  IdentPtr := Find(TK.Name);         { look up in table       }
  884.                  if IdentPtr^.Class <> Variable     { cannot be proc Ident   }
  885.                   then Error(XVar);
  886.                  if IdentPtr^.VarType <> IntSym     { only integers can...   }
  887.                   then Error(XInt);                 { be read in Dada        }
  888.                  Gen(Concat(TK.Name,' READ'));      { issue the call in Forth}
  889.                  GetTK;                             { eat up the Ident       }
  890.                 end;
  891.      WriteSym : begin                               { predefined WRITE proc  }
  892.                  GetTK;                             { eat token              }
  893.                  if TK.Code <> Ident                { must name variable...  }
  894.                   then Error(XIdent);               { to be written          }
  895.                  IdentPtr := Find(TK.Name);         { look it up             }
  896.                  if IdentPtr^.Class <> Variable     { cannot be Proc name    }
  897.                   then Error(XVar);
  898.                  if IdentPtr^.VarType <> IntSym     { only integers can...   }
  899.                   then Error(XInt);                 { be written             }
  900.                  Gen(Concat(TK.Name,' WRITE'));     { issue the call         }
  901.                  GetTK;                             { consume the Ident      }
  902.                 end;
  903.      else       Error(XStmt);                       { if none of the above   }
  904.     end;
  905.    end;
  906.  
  907.  
  908.     {    ParseBlock has two parts. It first checks for a procedure declar-
  909.          ation; if it finds one, it parses the header and calls itself again.
  910.          Ultimately, the BEGIN symbol that marks the statement part of a block
  911.          must be reached. Each statement is then processed in turn (by Parse-
  912.          Statement) until the matching END is reached. The possible nesting
  913.          of blocks within blocks is accommodated automatically by the re-
  914.          cursive organization of the routines. Recall that ParseBlock is passed
  915.          an identifier as an argument, namely the Ident of the procedure or
  916.          program that issued the call. This Ident is written into the code as
  917.          the designator of a Forth word when "begin" is reached.    }
  918.  
  919.   begin   { ParseBlock }
  920.    CurrentScope := CurrentScope + 1;             { bump up nesting count      }
  921.    while TK.Code = ProcSym do                    { proc declarations          }
  922.     begin
  923.      GetTK;                                      { eat "procedure" token      }
  924.      if TK.Code <> Ident then Error(XIdent);     { proc must have name        }
  925.      HoldID := TK.Name;                          { save to pass to next level }
  926.      Declare(TK.Name,Proc,Null);                 { put in table as proc name  }
  927.      GetTK;                                      { eat the Ident              }
  928.      if TK.Code <> Semi then Error(XSemi);       { must have a semi           }
  929.      GetTK;                                      { throw the semi away        }
  930.      ParseBlock(HoldID);                         { call again, pass proc name }
  931.      if TK.Code <> Semi then Error(XSemi);       { proc block must have semi  }
  932.      GetTK;                                      { eat it up                  }
  933.     end;
  934.    if TK.Code <> BeginSym then Error(XBegin);    { block begins "BEGIN"       }
  935.    Gen(Concat(': ',Caller));                     { start colon definition     }
  936.    GetTK;                                        { eat the "BEGIN"            }
  937.    while TK.Code <> EndSym do                    { loop for all statements    }
  938.     begin
  939.      ParseStatement;                             { call for each stmt         }
  940.      if not (TK.Code in [Semi,EndSym])           { separator or terminator... }
  941.       then Error(XSemEnd);                       { need after each one        }
  942.      if TK.Code = Semi then GetTK;               { if semi, eat & go back     }
  943.     end;                                         { TK must have been "END"    }
  944.    GetTK;                                        { eat the END                }
  945.    Gen(';'); Gen('|');                           { end Forth def, force CR    }
  946.    Blot;                                         { clean up symbol table      }
  947.   end;
  948.  
  949.  
  950.     {    ParseProgram sets the entire compiler in motion. It first handles
  951.          the program header, saving the program name (which will be the
  952.          last Forth word generated). The program is declared in the symbol
  953.          table as a procedure like any other, except that its scope field
  954.          has a value of zero, which no other procedure can have. ParseProgram
  955.          then calls ParseVariables and ParseBlock, which process the body
  956.          of the program. Finally there is a check for the final dot.    }
  957.  
  958.  begin   { ParseProgram }
  959.   if TK.Code <> PgmSym then Error(XPgm);        { must begin "PROGRAM"        }
  960.   GetTK;                                        { dispose of that token       }
  961.   if TK.Code <> Ident then Error(XIdent);       { program must have a name    }
  962.   HoldID := TK.Name;                            { save, pass to ParseBlock    }
  963.   Declare(TK.Name,Proc,Null);                   { install in table            }
  964.   GenHeader(TK.Name);                           { output the Forth prelude    }
  965.   GetTK;                                        { eat the Ident               }
  966.   if TK.Code <> Semi then Error(XSemi);         { header must end with semi   }
  967.   GetTK;                                        { toss out the semi           }
  968.   ParseVariables;                               { do the global declarations  }
  969.   ParseBlock(HoldID);                           { give Block the program name }
  970.   if TK.Code <> Dot then Error(XDot);           { not done until "." read     }
  971.   Gen(';S');                                    { tell Forth to stop          }
  972.  end;
  973.  
  974.  
  975. {*****************************************************************************}
  976. {*****************************************************************************}
  977. {*****************                                           *****************}
  978. {*****************               MAIN BLOCK                  *****************}
  979. {*****************                                           *****************}
  980. {*****************************************************************************}
  981. {*****************************************************************************}
  982.  
  983.     {    The main driver routine has little to do: initialize some global
  984.          variables, open the files and crank up the parser.     }
  985.  
  986. procedure Initialize;
  987.  begin
  988.   InitErrorList;            { fill up one static array... }
  989.   InitKeywords;             { and then another            }
  990.   InitSets;                 { define sets of tokens       }
  991.   InitOutBuf;               { set up a clean slate        }
  992.   FirstSym := nil;          { make pointer point nowhere  }
  993.   CurrentScope := 0;        { at start scope is global    }
  994.   LineCount := 1;           { start on first source line  }
  995.   SayHello;                 { paint the screen            }
  996.  end;
  997.  
  998. begin    { main block }
  999.  Initialize;
  1000.  OpenFiles;
  1001.  Read(InFile,CH); CH := Upcase(CH);    { get first char for scanner }
  1002.  GetTK;                                { and first token for parser }
  1003.  ParseProgram;
  1004.  CloseFiles;
  1005.  WriteLn('Compilation complete.');
  1006. end.
  1007.